<html>
  <head>
    <title>自定义UI   总览</title>
  </head>
  <body>
    <script>
      //  自定义UI  概览

            /*
                  文档解释了任何为asp.net core razor 页面应用程序"覆盖依赖" 的"应用程序模块" 或 "主题" 的用户交互界面 
            */

            /*
                一、覆盖页面

                          本节介绍 Razor Pages 开发，这是为Asp.net core 创建服务器陈轩用户交互界面的推荐方法。

                          预构建模块通常使用Razor pages方式，而不是典型的MVC模式。


                          你通常对页面有三种覆盖要求：

                                ①、覆盖"页面模型（C#）"端以执行附加逻辑而不更改页面UI

                                ②、仅覆盖Razor Page (.chtml文件)以更改UI，而不更改页面后面的模型(C#)

                                ③、完全覆盖页面


                          1、覆盖页面模型（C#）

                                          using System.Threading.Tasks;
                                          using Microsoft.AspNetCore.Mvc;
                                          using Volo.Abp.DependencyInjection;
                                          using Volo.Abp.Identity;
                                          using Volo.Abp.Identity.Web.Pages.Identity.Users;

                                          namespace Acme.BookStore.Web.Pages.Identity.Users
                                          {
                                              [Dependency(ReplaceServices = true)]  // 替换实现
                                              [ExposeServices(typeof(EditModalModel))]  // 暴露服务
                                              public class MyEditModalModel : EditModalModel
                                              {
                                                  public MyEditModalModel(
                                                      IIdentityUserAppService identityUserAppService,
                                                      IIdentityRoleAppService identityRoleAppService
                                                      ) : base(
                                                          identityUserAppService,
                                                          identityRoleAppService)
                                                  {
                                                  }

                                                  public async override Task<IActionResult> OnPostAsync()
                                                  {
                                                      //TODO: Additional logic
                                                      await base.OnPostAsync();
                                                      //TODO: Additional logic
                                                  }
                                              }
                                          }

                                          此类继承并替换用户的EditModalModel，并覆盖OnPostAsync方法以在底层代码之前和之后执行附加逻辑。

                                          它使用ExposeServices和Dependency属性来替换类。


                                
                        2、覆盖Razor页面   (.cshtml)

                                        通过在同一路径下创建新的.cshtml文件，可以覆盖.cshtml文件。
                                        
                                        
                                        eg:
                                              此示例覆盖由帐户模块定义的登录页面UI。

                                              帐户模块在Pages/Account文件夹下定义了一个Login.cshtml文件。
                                              
                                              因此，您可以通过在同一路径中创建一个文件来覆盖它：

                                                "Pages/Account/Login.cshtml"

                                              你通常希望复制模块原始的 .cshtml文件，然后进行必要的更改。

                                              你可以在此处找到原始文件。

                                              不要赋值Login.cshtml文件，这是Razor页面的代码文件，我们还不想覆盖它。

                                               （如果要覆盖的页面包含ABP标记助手，请不要忘记添加_ViewImports. cshtml。）
                                               
                                                      @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
                                                      @addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
                                                      @addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
                                                      @addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling

                                              就是这么简单，你可以随意更改文件内容。


                        3、完全覆盖剃刀页面

                                         你可能希望完全覆盖页面： Razor 页面与页面相关的C#文件。
                                         
                                         在这种情况下：

                                              ①、就像上面描述的那样覆盖页面模型类，但不要替换现有的页面模型类。

                                              ②、覆盖上面描述的Razor页面，但也更改@model指令以指向您的新页面模型。


                                        eg:  示例  此示例覆盖由帐户模块定义的登录页面。

                                              创建从LoginModel派生的页面模型类  在Volo.Abp.Account.Web.Pages.Account命名空间中定义）：

                                              （   如果您使用的是AbpAccountWebOpenIddictModule或AbpAccountPublicWebOpenIddictModule，
                                                
                                                   则基类是OpenIddictSupportedLoginModel而不是LoginModel。
                                                   
                                                   您应该将ExposeServices属性更改为[ExposeServices(typeof (MyLoginModel), typeof(OpenIddictSupportedLoginModel), typeof(LoginModel))]
                                                  
                                                ）

                                                        [ExposeServices(typeof (MyLoginModel), typeof(LoginModel))]
                                                        public class MyLoginModel : LoginModel
                                                        {
                                                            public MyLoginModel(
                                                                IAuthenticationSchemeProvider schemeProvider,
                                                                IOptions<AbpAccountOptions> accountOptions
                                                                ) : base(
                                                                schemeProvider,
                                                                accountOptions)
                                                            {

                                                            }
   
                                                            public override Task<IActionResult> OnPostAsync(string action)
                                                            {
                                                                // 添加逻辑
                                                                return base.OnPostAsync(action);
                                                            }

                                                            //TODO: Add new methods and properties...
                                                        }

                                                如果需要，您可以覆盖任何方法或添加新的属性/方法。

                                                如上所述，将Login.cshtml文件复制到您的解决方案中。更改@model指令以指向MyLoginModel：

                                                         @page
                                                          ...
                                                          @model Acme.BookStore.Web.Pages.Account.MyLoginModel
                                                          ...      
                                                          
                                                          
                                                就是这么简单，在视图中进行任何更改并运行你的应用程序。



                                        ★、无需继承即可替换页面模型

                                                        你不必就从原始页面模型继承（就像前面的示例中所做的那样）。

                                                        相反，你可以自己完全重新实现页面。
                                                        
                                                        在这种情况下，只需要从 PageModel、AbpPageModel 或者你需要的任何适合的基类派生。
            */

            /*
                      二、覆盖视图组件

                              1、如何覆盖组件

                                        ABP预构建的主题和模块定义了一些"可重用的视图组件"。

                                        这些视图组件可以像上面描述的页面一样被替换。


                                        下面的屏幕截图取自应用程序启动模板附带的基本主题。

                                        基本主题为布局定义了一些视图组件。例如，上面带有红色矩形的突出显示区域称为品牌组件。

                                        您可能希望通过添加自己的应用程序徽标来自定义此组件。让我们看看如何做到这一点。

                                        首先，创建您的徽标并放置在Web应用程序的文件夹下：

                                        我们使用了wwwroot/logos/bookstore-logo.png路径。

                                        然后从Themes/Basic/Components/Brand文件夹下的基本主题文件中复制品牌组件的视图（from here）。

                                        结果应该类似于下图：

                                        然后根据需要更改Default.cshtml。示例内容可以是这样的：

                                                  <a href="/">
                                                      <img src="~/logos/bookstore-logo.png" width="250" height="60"/>
                                                  </a>  

                                       
                                                  
                                2、覆盖主题

                                          如上所述，您可以替换所用主题的任何组件、布局或c#类。有关主题系统的更多信息，请参阅主题文档。
            */

            /*
                        三、覆盖静态资源

                                  覆盖模块的静态嵌入式资源（如JavaScript、Css或图像文件）非常容易。只需将文件放在解决方案中的相同路径中，然后让Virtual File System来处理它。  
            */

            /*
                        四、操纵捆绑包

                                 捆绑和缩小系统提供了一个可扩展和动态的系统来创建script和style包。它允许您扩展和操作现有的包。 
                                 
                                 
                                  1、添加全局CSS文件

                                                  例如，ABP定义了一个 "全局样式包"，它被添加到每一个页面（实际上是由主题添加到布局中）。

                                                  让我们在包文件的末尾添加一个自定义样式文件，这样我们就可以覆盖任何全局样式。

                                                  首先，创建一个CSS文件并将其定位在wwwroot内的文件夹中：

                                                  在文件中定义一些自定义CSS规则。例子：

                                                              .card-title {
                                                                  color: orange;
                                                                  font-size: 2em;
                                                                  text-decoration: underline;
                                                              }

                                                              .btn-primary {
                                                                  background-color: red;
                                                              }

                                                  然后将此文件添加到模块的ConfigureServices方法中的标准全局样式包中：

                                                              Configure<AbpBundlingOptions>(options =>
                                                              {
                                                                  options.StyleBundles.Configure(
                                                                      StandardBundles.Styles.Global, //The bundle name!
                                                                      bundleConfiguration =>
                                                                      {
                                                                          bundleConfiguration.AddFiles("/styles/my-global-styles.css");
                                                                      }
                                                                  );
                                                              });


                                  2、操作捆绑文件

                                             上面的示例将一个新文件添加到捆绑包中。如果您创建一个bundle贡献者类，您可以做更多事情。例子：
                                             
                                                          public class MyGlobalStyleBundleContributor : BundleContributor
                                                          {
                                                              public override void ConfigureBundle(BundleConfigurationContext context)
                                                              {
                                                                  context.Files.Clear();
                                                                  context.Files.Add("/styles/my-global-styles.css");
                                                              }
                                                          }    
                                                              
                                              然后你可以将贡献者添加到现有捆绑包中：

                                                          Conf igure<AbpBundlingOptions>(options =>
                                                          {
                                                              options.StyleBundles.Configure(
                                                                  StandardBundles.Styles.Global,
                                                                  bundleConfiguration =>
                                                                  {
                                                                      bundleConfiguration.AddContributors(typeof(MyGlobalStyleBundleContributor));
                                                                  }
                                                              );
                                                          });

                                              清除所有CSS文件不是一个好主意。在现实世界的场景中，您可以查找特定文件并将其替换为您自己的文件。




                                  3、为特定页面添加JavaScript

                                              上面的示例适用于添加到布局中的全局包。

                                              如果要为依赖模块内定义的特定页面添加CSS/JavaScript文件（或替换文件）怎么办？

                                              假设您想在用户进入身份模块的角色管理页面后运行JavaScript代码。

                                              首先，在wwwroot、Pages或Views文件夹下创建一个标准JavaScript文件。

                                              ABP默认支持在这些文件夹中添加静态资源）。我们更喜欢Pages/Identity/Roles文件夹来遵循约定：

                                              文件内容很简单：

                                                      $(function() {
                                                          abp.log.info('My custom role script file has been loaded!');
                                                      });

                                              然后将此文件添加到角色管理页面的捆绑包中：

                                                      Configure<AbpBundlingOptions>(options =>
                                                      {
                                                          options.ScriptBundles
                                                              .Configure(
                                                                  typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName,
                                                                  bundleConfig =>
                                                                  {
                                                                      bundleConfig.AddFiles("/Pages/Identity/Roles/my-role-script.js");
                                                                  });
                                                      });

                                                  typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName是获取角色管理页面包名称的安全方法。

                                                  （请注意，并非每个页面都定义了这样的页面包。它们仅在需要时定义。）

                                                  除了向页面添加新的CSS/JavaScript文件外，您还可以替换现有文件（通过定义捆绑贡献者）。

            */

            /*
                    六、布局定制  Layout Customization

                                布局由主题定义（请参阅主题）由设计。

                                它们不包含在下载的应用程序解决方案中。

                                通过这种方式，您可以轻松地升级主题并获得新功能。

                                您不能直接更改应用程序中的布局代码，除非您将其替换为自己的布局（将在下一节中解释）。


                                有一些常见的方法可以自定义下一节中描述的布局。


                                1、菜单贡献者  Menu Contributors

                                              ABP定义了两个标准菜单：

                                                  StandardMenus.Main：应用程序的主菜单。
                                                  
                                                  StandardMenus.User：用户菜单（通常在屏幕右上角）。

                                            渲染菜单是主题的责任，但菜单项由模块和您的应用程序代码决定。
                                            
                                            只需实现IMenuContributor接口并在ConfigureMenuAsync方法中操作菜单项。

                                            菜单贡献者在需要渲染菜单时执行。
                                            
                                            应用程序启动模板中已经定义了一个菜单贡献者，因此您可以将其作为示例，并在必要时进行改进。有关详细信息，请参阅导航菜单文档。


                                2、Toolbar Contributors 工具栏贡献者


                                          (1)、如何自定义工具栏项目：

                                                        工具栏系统用于在用户交互界面上定义工具栏。

                                                        模块（或您的应用程序）可以将项目添加到工具栏，然后主题在布局上呈现工具栏。

                                                        只有一个标准工具栏（名为"Main"-定义为常量：StandardToolbars.Main）。对于基本主题，呈现如下所示：

                                                        在上面的屏幕截图中，主工具栏中添加了两个项目：语言切换组件和用户菜单。您可以在此处添加自己的项目。

                                                        eg:  添加通知图标

                                                              在此示例中，我们将在语言切换项的左侧添加一个通知（铃声）图标。
                                                              
                                                              工具栏中的项目应该是视图组件。
                                                              
                                                            ①、  因此，首先，在您的项目中创建一个新的视图组件：


                                                            ②、 NotificationViewComponent.cs：

                                                                      // 工具栏项目模型，视图组件
                                                                      public class NotificationViewComponent : AbpViewComponent
                                                                      {
                                                                          public async Task<IViewComponentResult> InvokeAsync()
                                                                          {
                                                                              return View("/Pages/Shared/Components/Notification/Default.cshtml");
                                                                          }
                                                                      }

                                                            ③、 Default.cshtml
                                                                      // 视图内容
                                                                      <div id="MainNotificationIcon" style="color: white; margin: 8px;">
                                                                          <i class="far fa-bell"></i>
                                                                      </div>

                                                             ④、 现在，我们可以创建一个实现IToolbarContributor接口的类：

                                                                        // 声明工具栏贡献者
                                                                        public class MyToolbarContributor : IToolbarContributor  // 工具栏贡献者
                                                                        {
                                                                            public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
                                                                            {
                                                                                if (context.Toolbar.Name == StandardToolbars.Main)
                                                                                {
                                                                                    // 添加工具栏项目
                                                                                    context.Toolbar.Items
                                                                                        .Insert(0, new ToolbarItem(typeof(NotificationViewComponent))); // 指向视图组件
                                                                                }

                                                                                return Task.CompletedTask;
                                                                            }
                                                                        }

                                                              ⑤、 最后，您需要将此贡献者添加到AbpToolbarOptions，在模块的ConfigureServices中：

                                                                        // 注册工具栏贡献者
                                                                        Configure<AbpToolbarOptions>(options =>
                                                                        {
                                                                            options.Contributors.Add(new MyToolbarContributor());
                                                                        });

                                                                就是这么简单，当你运行应用程序时，你将在工具栏上看到通知图标。

                                                                
                                                                NotificationViewComponent只是返回一个没有任何数据的视图。现实生活中，您可能希望查询数据库（或调用HTTP API）以获取通知并传递给视图。

                                                                如果需要，您可以将JavaScript或CSS文件添加到工具栏项的全局包（如前所述）中。


                                            (2)、布局挂钩  Layout Hooks  

                                                                Layout Hooks系统允许您在布局的某些特定部分添加代码。

                                                                所有主题的所有布局都应实现这些挂钩。

                                                                然后您可以将视图组件添加到挂钩点中。

                                                          eg: 添加Google Analytics脚本

                                                                  假设您需要将Google Analytics脚本添加到布局中（可用于所有页面）。
                                                                  
                                                                 ①、 首先，在您的项目中创建一个视图组件：


                                                                 ②、 GoogleAnalyticsViewComponent.cs

                                                                            // 创建视图组件类
                                                                            public class GoogleAnalyticsViewComponent : AbpViewComponent
                                                                            {
                                                                                public IViewComponentResult Invoke()
                                                                                {
                                                                                    return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml");
                                                                                }
                                                                            }
                                                     
                                                                  ③、 Default.cshtml

                                                                            <、script>
                                                                                (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
                                                                                        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
                                                                                        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
                                                                                })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

                                                                                ga('create', 'UA-xxxxxx-1', 'auto');
                                                                                ga('send', 'pageview');
                                                                            </、script>

                                                                            使用您自己的代码更改UA-xxxxxx-1。

                                                                  ④、然后，您可以将此组件添加到模块的ConfigureServices中的任何挂钩点：
                                                                  
                                                                            Configure<AbpLayoutHookOptions>(options =>
                                                                            {
                                                                                options.Add(
                                                                                    LayoutHooks.Head.Last, //布局挂钩名称
                                                                                    typeof(GoogleAnalyticsViewComponent) // 要添加的视图组件
                                                                                );
                                                                            });

                                                                  现在，GA代码将作为最后一项插入到页面的head中。

                                                                  您（或您正在使用的模块）可以将多个项目添加到同一个挂钩中。所有这些都将被添加到布局中。

                                                                  上面的配置将GoogleAnalyticsViewComponent添加到所有布局。您可能只想添加到特定布局：

                                                                            Configure<AbpLayoutHookOptions>(options =>
                                                                            {
                                                                                options.Add(
                                                                                    LayoutHooks.Head.Last,
                                                                                    typeof(GoogleAnalyticsViewComponent),
                                                                                    layout: StandardLayouts.Application //  设置要添加到的布局
                                                                                );
                                                                            }); 
                                3、布局 Layouts

                                                布局系统允许主题定义 标准的、命名的布局，并允许任何页面为其目的选择适当的布局。

                                                有三种预定义的布局：

                                                          ①、 Application:  应用程序的主要(和默认)布局。 它通常包含页眉、菜单（侧边栏）、页脚、工具栏...等。

                                                          ②、 Account :  此布局用于登录、注册 和其他类似页面使用。 默认用于 /Pages/Account文件夹下的页面。

                                                          ③、 Empty:    空的最小布局。

                                                这些名称在 StandardLayouts类中定义为常量。
                                                
                                                你绝对可以创建自己的布局，但这些事标准布局名称，由所有开箱即用的主题实现。

                                               (1)、布局位置  Layout Location

                                                        您可以在此处找到基本主题的布局文件。
                                                        
                                                        您可以将它们作为参考来构建自己的布局，也可以在必要时覆盖它们。   
                                                        
                                               (2)、ITheme 主题
                                                                              
                                                        ABP使用ITheme服务通过布局名称获取布局位置。您可以替换此服务以动态选择布局位置。

                                               (3)、IThemeManager 主题管理器

                                                        IThemeManager用于获取当前主题并获取布局路径。
                                                        
                                                        任何页面都可以确定自己的布局。例子：

                                                                @using Volo.Abp.AspNetCore.Mvc.UI.Theming
                                                                @inject IThemeManager ThemeManager
                                                                @{
                                                                    // 确定当前页面使用哪个布局
                                                                    Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty);
                                                                }

                                                        此页面将使用空布局。您使用ThemeManager.CurrentTheme.GetEmptyLayout();扩展方法作为快捷方式。


                                                        如果要为特定文件夹下的所有页面设置布局，请将上面的代码写入该文件夹下的_ViewStart.cshtml文件中。
            */
    </script>
  </body>
</html>